#!/bin/bash
# set -e
if [ -n "$DEBUG" ]; then
    set -x
fi
ROOT_DIR=$(dirname $(readlink -f "$0"))
source "$ROOT_DIR/env.sh"
SELF=$(readlink -f "$0")
# DIFF_DIR=$(realpath "$1")
MODE="$1"
SHELL=${SHELL:-/bin/bash}
shift 1 || true
ARGS=("${@}")

export RAW_SELF=${RAW_SELF:-$SELF}
export RAW_DPKG_EXEC=${RAW_DPKG_EXEC:-$DPKG_EXEC}
RAW_HOST_FS_DIR=/run/host/rootfs
HOST_FS_DIR=${HOST_FS_DIR:-$RAW_HOST_FS_DIR}
ROOT_FS_DIR=${ROOT_FS_DIR:-}

LINGLONG_SRC_DIR=$ROOT_FS_DIR/project/linglong/sources
LL_WORK_DIR=$(pwd)
OVERLAY_DIR=$LL_WORK_DIR/filesystem
MERGED_DIR=$OVERLAY_DIR/merged
UPPER_DIR=$OVERLAY_DIR/upper
WORK_DIR=$OVERLAY_DIR/work
OLDFS_DIR=$MERGED_DIR/rootfs

# export PATH=$PATH:$HOST_FS_DIR/bin:$HOST_FS_DIR/sbin
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOST_FS_DIR/usr/lib/x86_64-linux-gnu

function mount_rootfs() {
    # lower, merged
    rm -rf $OVERLAY_DIR
    lowerdir="$1"
    mergeddir="$2"
    mkdir -p "$UPPER_DIR" "$WORK_DIR" "$1" "$2"
    fuse-overlayfs -o "lowerdir=$lowerdir,upperdir=$UPPER_DIR,workdir=$WORK_DIR,squash_to_root" "$mergeddir"
}
function mount_fuse() {
    touch /dev/fuse
    mount --bind $HOST_FS_DIR/dev/fuse /dev/fuse
}
function mount_data() {
    mount --rbind ${ROOT_FS_DIR}/dev $MERGED_DIR/dev
    DPKG_EXEC="$RAW_DPKG_EXEC" perl -pe 's/{DPKG_EXEC}/$ENV{DPKG_EXEC}/' "$APT_CONF" >"$LL_WORK_DIR/apt.conf"
    touch "$MERGED_DIR/etc/apt/apt.conf.d/ll-killer"
    mount --bind "$LL_WORK_DIR/apt.conf" "$MERGED_DIR/etc/apt/apt.conf.d/ll-killer"
    if [ -f apt.conf ]; then
        touch $MERGED_DIR/etc/apt/apt.conf.d/config
        mount --bind apt.conf $MERGED_DIR/etc/apt/apt.conf.d/config
    fi
    if [ -f sources.list ]; then
        mount --bind sources.list $MERGED_DIR/etc/apt/sources.list
    fi
    if [ -d sources.list.d ]; then
        mount --rbind sources.list.d $MERGED_DIR/etc/apt/sources.list.d
    fi
    mkdir -p $LINGLONG_SRC_DIR
    mount --rbind $LINGLONG_SRC_DIR $MERGED_DIR/var/cache/apt/archives
    mount --rbind $ROOT_FS_DIR/tmp $MERGED_DIR/tmp
    mount --rbind $ROOT_FS_DIR/home $MERGED_DIR/home
    mount --rbind $ROOT_FS_DIR/proc $MERGED_DIR/proc
    mkdir $MERGED_DIR/project 2>/dev/null && mount --rbind $ROOT_FS_DIR/project $MERGED_DIR/project || true
}
function clean() {
    find "$UPPER_DIR" '(' -type c -or -name ".wh..*opq" ')' -exec rm -f {} \;
    rm -rf "$UPPER_DIR/rootfs" "$UPPER_DIR/var/cache"
}
function copy() {
    cp -arfT "$UPPER_DIR" "$PREFIX"
}
function reexec() {
    # mode cmd ...args
    next=$1
    shift
    exec "$@" "$SELF" "$next" "${ARGS[@]}"
}
function help_message() {
    printf "
用法: ll-killer mode [...args]

模式说明:
  build-and-check                       构建并自动补全依赖
  ldd-check <output>                    检查动态库依赖并记录日志
                                        运行前必须至少使用ll-builder build构建一次项目
  ldd-search <input> [found] [missing] 搜索动态库依赖
                                        主机上必须安装apt-file
                                        输出到 ldd-missing.log 和 ldd-found.log。
  local  [...args]                      切换到隔离的APT环境。
  generate <package.info>               生成linglong.yaml脚本。
  shell                                 执行交互式 shell。
  *                                     显示本帮助信息。

ll-builder build 构建模式容器内模式:
  root            切换到 root 模式，全局可写。
  build           执行安装和构建脚本。
  dpkg-install    使用 dpkg 安装模式安装sources目录下的deb。
  extract         使用 dpkg 解压模式安装sources目录下的deb。
  clean           清理搜集的依赖文件和目录。
  copy            拷贝收集的依赖到\$PREFIX。
  install         清理并拷贝文件（clean + copy）。
  setup           配置应用的快捷方式和图标文件。
  dev             切换到隔离环境。
  --              切换到默认的 root 模式。

ll-killer 内部模式，除非你知道是什么，否则不要使用：
  mount           挂载 FUSE 和根文件系统，准备合并目录。
  pivot_root      切换根文件系统到合并目录，并执行 shell。
  local-env       配置本地 APT 环境，绑定相关目录，更新包信息。
  dev-host        配置开发主机环境，并切换根文件系统。

示例:
  ll-killer generate package.info       通过package.info生成linglong.yaml
  ll-killer build-and-check             构建并自动补全依赖
  ll-killer ldd-check ldd-check.log     检查容器内是否有缺失依赖，输出缺失文件名到ldd-check.log
  ll-killer ldd-search ldd-check.log ldd-found.log ldd-missing.log 
                                        搜索ldd-check.log中的依赖
  ll-killer -- bash                     在容器内切换到root模式                
                       
"
}
case "$MODE" in
root)
    reexec mount unshare -rm
    ;;
mount)
    mount_fuse
    mount_rootfs "${ROOT_FS_DIR:-/}" "$MERGED_DIR"
    mount_data
    reexec pivot_root unshare -m
    ;;
pivot_root)
    mkdir -p $OLDFS_DIR
    pivot_root "$MERGED_DIR" "$OLDFS_DIR"
    ROOT_FS_DIR= HOST_FS_DIR=$RAW_HOST_FS_DIR SELF=$RAW_SELF reexec shell
    ;;
shell)
    exec "${ARGS[@]:-$SHELL}"
    ;;
clean)
    clean
    ;;
copy)
    copy
    ;;
install)
    clean
    copy
    ;;
build)
    exec "$SCRIPT_DIR/install.sh" "$SCRIPT_DIR/build.sh" "$@"
    ;;
build-and-check)
    exec "$SCRIPT_DIR/build-and-check.sh" "$@"
    ;;
ldd-check)
    exec ll-builder run --exec "entrypoint.sh $ROOT_DIR/ldd-check.sh" | tr -d '\r' | tee ldd-check.log
    ;;
ldd-search)
    exec "$SELF" local "$ROOT_DIR/ldd-search.sh" "$@"
    ;;
local)
    reexec local-env unshare -rm
    ;;
local-env)
    APT_TMP_DIR=$(mktemp -d ll-killer.XXXXXX -p /tmp)
    mkdir -p sources.list.d "$APT_TMP_DIR/apt" "$APT_TMP_DIR/cache"
    mount --bind ./sources.list /etc/apt/sources.list
    mount --rbind ./sources.list.d /etc/apt/sources.list.d
    mount --rbind "$APT_TMP_DIR/apt" /var/lib/apt
    mount --rbind "$APT_TMP_DIR/cache" /var/cache
    apt -o APT::Sandbox::User="root" update -y
    reexec shell
    ;;
generate)
    ./scripts/pkg-info.sh APT-Sources >sources.list
    exec "$SELF" local "$ROOT_DIR/generate.sh" "$@"
    ;;
setup)
    exec "$ROOT_DIR/setup.sh" "$@"
    ;;
extract)
    exec "$ROOT_DIR/extract.sh" "$@"
    ;;
dpkg-install)
    exec "$SELF" -- "$ROOT_DIR/dpkg-install.sh" "$@"
    ;;
dev-host)
    mkdir -p /rootfs
    mount --rbind $HOST_FS_DIR /rootfs
    mount --rbind /tmp /rootfs/tmp
    mkdir -p /tmp/llfs
    pivot_root /rootfs /rootfs/tmp/llfs
    HOST_FS_DIR=/ ROOT_FS_DIR=/tmp/llfs SELF=/tmp/llfs/$SELF reexec mount
    ;;
dev)
    reexec dev-host unshare -rm
    ;;
--)
    # reexec dev
    reexec root
    ;;
help)
    help_message
    ;;
*)
    help_message
    ;;
esac
